package com.keguoyu.easymvp.ioc;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import androidx.annotation.NonNull;

import com.google.common.reflect.Reflection;
import com.keguoyu.easymvp.commons.PluginFactory;

@SuppressWarnings("unchecked")
final class InstanceManager<T> {
  private final Map<Class<? extends T>, Collection<ImplMapper>> mCollectionMap;
  private final Map<Class<? extends T>, PluginFactory<? extends T>> mPluginFactories;
  private final Map<Class<? extends T>, PluginFactory<? extends T>> mCustomPluginFactories;
  private final boolean mProxy;

  private final InvocationHandler mInvocationHandler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Class<?> retType = method.getReturnType();
      if (ClassHelper.isInteger(retType)) {
        return 0;
      } else if (ClassHelper.isBoolean(retType)) {
        return false;
      } else if (ClassHelper.isString(retType)) {
        return "";
      } else if (retType.isInterface()) {
        Annotation anno = method.getAnnotation(NonNull.class);
        return anno != null ? Reflection.newProxy(retType, this) : null;
      }
      return null;
    }
  };

  public InstanceManager(Map<Class<? extends T>, Collection<ImplMapper>> collectionMap, boolean proxy) {
    mCollectionMap = collectionMap;
    mPluginFactories = processRegisterInfo();
    mProxy = proxy;
    mCustomPluginFactories = new HashMap<>();
  }

  public void register(Class<? extends T> clazz, PluginFactory<? extends T> factory) {
    mCustomPluginFactories.put(clazz, factory);
  }

  private Map<Class<? extends T>, PluginFactory<? extends T>> processRegisterInfo() {
    Map<Class<? extends T>, PluginFactory<? extends T>> mapping = new HashMap<>();
    for (Class<? extends T> interfaceClass: mCollectionMap.keySet()) {
      Collection<ImplMapper> mappers = mCollectionMap.get(interfaceClass);
      if (mappers != null && mappers.size() > 0) {
        PluginFactory<? extends T> bestFitImpl = findBestFitImpl(mappers);
        if (bestFitImpl != null) {
          mapping.put(interfaceClass, bestFitImpl);
        }
      }
    }
    return mapping;
  }

  private PluginFactory<? extends T> findBestFitImpl(Collection<ImplMapper> list) {
    List<ImplMapper> sorted = new ArrayList<>(list);
    Collections.sort(sorted, (o1, o2) -> Integer.compare(o1.mPriority, o2.mPriority));
    ImplMapper target = sorted.get(0);
    return target != null ? (PluginFactory<? extends T>) target.mPluginFactory : null;
  }

  public <B extends T> B get(Class<B> clss) {
    if (mPluginFactories.containsKey(clss)) {
      return (B) mPluginFactories.get(clss).getInstance();
    } else if (mCustomPluginFactories.containsKey(clss)) {
      return (B) mCustomPluginFactories.get(clss).getInstance();
    }
    return getDefaultImpl(clss);
  }

  private <B extends T> B getDefaultImpl(Class<B> clss) {
    if (!mProxy) {
      return null;
    }
    return (B) Proxy.newProxyInstance(clss.getClassLoader(), new Class<?>[]{clss}, mInvocationHandler);
  }
}
